/*
 NokiaLCD.cpp - Library for a Nokia LCD with the epson driver.
 Created by Thomas Jespersen, July 2009 (Originally Arduino Sketch by Gravitech.us)
 Released into the public domain.
 
 Interface LCD6610 to Arduino Nano
 Nano --> LCD6610
 D2 --> BL
 D3 --> #CS
 D4 --> SCLK
 D5 --> SDATA
 D6 --> #RESEET
 +5V --> VCC,VBL
 GND --> GND
 */

//#include "WProgram.h"
#include "NokiaLCD.h"

/*************************************************************/
/* Function definitions */
/*************************************************************/

/**************************************/
/* Sending data */
/**************************************/
void NokiaLCD::dat(byte data) 
{
	_cs.set();
	SPCR = 0;  //turn of the spi controller

	PORTB |= _BV(PB5);	//set the data bit high for data
	PORTB |= _BV(PB7);  //set the clock high
	PORTB &= ~_BV(PB7); //set the clock low

	spiTx(data);	//send the rest of the packet
	_cs.clear();

}

/**************************************/
/* Sending command */
/**************************************/
void NokiaLCD::cmd(byte data) 
{
	

	_cs.set();
	SPCR = 0;  //turn of the spi controller

	PORTB &= ~_BV(PB5); //set data bit low for command
	PORTB |= _BV(PB7);  //set the clock high
	PORTB &= ~_BV(PB7); //set the clock low

	spiTx(data);	//send the rest of the packet
	_cs.clear();
}

void NokiaLCD::spiTx(unsigned char v)
{
	SPCR = _BV(SPE) | _BV(MSTR) | _BV(SPR0); //Turn SPI on
	
	SPDR = v;
	while(!(SPSR & _BV(SPIF)))
	{}
}

void NokiaLCD::reset(void)
{
	_rs.set();
	delay(1000);
	_rs.clear();
	delay(1000);
}

void NokiaLCD::init(void) 
{
	DDRB |= _BV(PB5) | _BV(PB7);
	PORTB |= _BV(PB5) | _BV(PB7);
	
	_cs.setup(PB, 4, OUTPUT);
	_cs.setInvert(TRUE);
	_cs.clear();

	_rs.setup(PB, 0, OUTPUT);
	_rs.setInvert(TRUE);
	_rs.clear();
	
	reset();
	
    cmd(EP_DISCTL);  	// display control(EPSON)
    dat(0x0C);   		// 12 = 1100 - CL dividing ratio [don't divide] switching period 8H (default)
	dat(0x20);			// 130 line display
	dat(0x00);			// no inversely highlighted lines
	dat(0x01);			// dispersion
	
    cmd(EP_COMSCN);  	// common scanning direction(EPSON)
    dat(0x01);
    
    cmd(EP_OSCON);  	// internal oscillator ON(EPSON)
	
    cmd(EP_SLPOUT);  	// sleep out(EPSON)

    cmd(EP_PWRCTR); 	// power ctrl(EPSON)
    dat(0x0F);    		//everything on, no external reference resistors

//	cmd(EP_DISINV);  	// invert display mode(EPSON)

    cmd(EP_DATCTL);  	// data control(EPSON)
    dat(0x00);			// normal column addressing
	dat(0x00);   		// normal RGB arrangement
	dat(0x02);			// 16-bit Grayscale Type A
	
    cmd(EP_VOLCTR);  	// electronic volume, this is the contrast/brightness(EPSON)
	dat(0x21);   		// volume (contrast) setting - fine tuning, original
    dat(0x03);   		// internal resistor ratio - coarse adjustment

    cmd(EP_NOP);  	// nop(EPSON)

	delay(1000);
	
    cmd(EP_DISON);   	// display on(EPSON)
}

void NokiaLCD::setWindow(int x1, int y1, int x2, int y2) 
{
	cmd(LCD_PASET);   // page start/end ram
	dat(y1 + LCD_Y_OFFSET);
	dat(y2 + LCD_Y_OFFSET);

	cmd(LCD_CASET);   // column start/end ram
	dat(x1);
	dat(x2);
}

void NokiaLCD::beginWrite(void)
{
	cmd(LCD_RAMWR);
}

void NokiaLCD::writeWindow(int x1, int y1, int x2, int y2) 
{
	setWindow(x1, y1, x2, y2);
	beginWrite();
}

void NokiaLCD::writePair(int c1, int c2) 
{
	dat(c1 >> 4);
	dat((c1<<4) | ((c2>>8) & 0x0f));
	dat(c2);
}

void NokiaLCD::setPixel(int color, int x, int y) 
{
	writeWindow(x, y, x, y);
	writePair(0, color);
}

void NokiaLCD::fillBox(int color, int x1, int y1, int x2, int y2) 
{
	uint8_t c1 = color >> 4;
	uint8_t c2 = (color<<4) | ((color>>8) & 0x0f);
	uint8_t c3 = color;
	int num_words = ((x2-x1+1) * (y2-y1+1) + 1) / 2;

	writeWindow(x1, y1, x2, y2);
	for(int i=0; i<num_words; i++) {
		dat(c1);
		dat(c2);
		dat(c3);
	}
}

void NokiaLCD::clearScreen(int color) 
{
	fillBox(color, 0, 0, LCD_WIDTH-1, LCD_HEIGHT-1);
}

void NokiaLCD::drawChar(char c, int fg, int bg, int x, int y) 
{
	int idx = c-' ';
	if(idx < 0 || idx >= 96) idx = 0;
	const char *p = font5x8 + idx*5;

	writeWindow(x, y, x+5, y+8);
	for(int j=0; j<8; j++) 
	{
		char mask = (1<<j);
		for(int i=0; i<3; i++) 
		{
			char b1 = p[i*2] & mask;
			char b2 = (i==2) ? 0 : p[i*2+1] & mask;
			writePair(b1?fg:bg, b2?fg:bg);
		}
	}
}

//void drawChar(char c, int fg, int bg, int x, int y) {
//	int fp = c-' ';
//	if(fp < 0 || fp >= 96) fp = 0;
//
//	for(int j=0; j<8; j++) {
//		uint8_t mask = (1<<j);
//		for(int i=0; i<5; i++) {
//			uint8_t b = font5x8[fp*5 + i] & mask;
//			LCDSetPixel(b?fg:bg, x+i, y+j);
//			//LCDSetPixel(0x0f0, x+i, y+j);
//		}
//	}
//}

void NokiaLCD::drawString(char *s, int fg, int bg, int x, int y) 
{
	while(*s) 
	{
		drawChar(*s, fg, bg, x, y);
		x += 6;
		s++;
	}
}

void NokiaLCD::testPattern(void)
{
	for(int color_idx=0; ; color_idx=(color_idx+1)%16) {
		int fg = 
			((color_idx & 1) ? 0x00f : 0) |
			((color_idx & 2) ? 0x0f0 : 0) |
			((color_idx & 4) ? 0xf00 : 0);
		int bg = 0;
		if(color_idx & 8) {
			bg = fg;
			fg = 0;
		}
		if(fg == bg) continue;

		clearScreen(bg);

		// Draw dotted-line border around edge of display.
		// This is a way to check that LCD_WIDTH, LCD_HEIGHT, and
		// LCD_Y_OFFSET are set to proper values.
		for(int i=0; i<LCD_WIDTH; i++) {
			if(i & 1) {
				setPixel(fg, i, 0);
				setPixel(fg, i, LCD_HEIGHT-1);
			}
		}
		for(int i=0; i<LCD_HEIGHT; i++) {
			if(i & 1) {
				setPixel(fg, 0, i);
				setPixel(fg, LCD_WIDTH-1, i);
			}
		}

		// test box drawing for various size boxes
		int x, y;
		y = 2;
		for(int j=0; j<4; j++) {
			x = 2;
			for(int i=0; i<4; i++) {
				fillBox(fg, x, y, x+i, y+j);
				fillBox(fg, x+17, y+1, x+17+i, y+1+j);
				x += i+2;
			}
			y += j+2;
		}

		// test color gradient - horizontal
		x = 36;
		y = 3;
		for(int i=0; i<16; i++) {
			fillBox((fg|bg) & (i*0x111), x+i, y, x+i, y+15);
		}

		// test color gradient - vertical
		x = 54;
		y = 3;
		for(int i=0; i<16; i++) {
			fillBox((fg|bg) & (i*0x111), x, y+i, x+15, y+i);
		}

		// test font and bitmap drawing at various offsets
		char *txt = "NOKIA";
		x = 6;
		y = 18;
		for(int i=0; i<5; i++) {
			drawChar(txt[i], fg, bg, x+i*6, y+i);
			drawChar(txt[4-i], fg, bg, x+24-i, y+4+i*8);
			drawChar(txt[i], fg, bg, x+20-i*6, y+36-i);
			drawChar(txt[4-i], fg, bg, x-4+i, y+32-i*8);
		}

		// Sierpinski triangle, a test of bitmap drawing.
		x = 39;
		y = 20;
		writeWindow(x, y, x+63, y+63);
		for(int j=0; j<64; j++) {
			for(int i=0; i<64; ) {
				int b1 = (j & i) ? bg : fg;
				i++;
				int b2 = (j & i) ? bg : fg;
				i++;
				writePair(b1, b2);
			}
		}

		// binary test patten - vertical
		x = 110;
		y = 3;
		writeWindow(x, y, x+7, y+63);
		for(int j=0; j<64; j++) {
			for(int i=0; i<8;) {
				int b1 = (j & (1<<i)) ? fg : bg;
				i++;
				int b2 = (j & (1<<i)) ? fg : bg;
				i++;
				writePair(b1, b2);
			}
		}

		// binary test patten - horizontal
		x = 59;
		y = 74;
		writeWindow(x, y, x+63, y+7);
		for(int j=0; j<8; j++) {
			for(int i=0; i<64;) {
				int b1 = (i & (1<<j)) ? fg : bg;
				i++;
				int b2 = (i & (1<<j)) ? fg : bg;
				i++;
				writePair(b1, b2);
			}
		}

		// another color gradient
		x = 60;
		y = 93;
		int c0 = (fg|bg);
		int c1 = (c0 << 4) | (0x0f & (c0 >> 8));
		int c2 = (c0 << 8) | (0xff & (c0 >> 4));
		c2 &= ~c1;
		for(int j=0; j<16; j++) {
			for(int i=0; i<16; i++) {
				setPixel(
					(c1 & (i*0x111)) | (c2 & (j*0x111)),
					x+i, y+j);
			}
		}

		// Checkerboard.  Tests setting of single pixels
		// at various offsets.
		x = 80;
		y = 93;
		for(int j=0; j<7; j++) {
			for(int i=0; i<7; i++) {
				if((i&1) == (j&1)) {
					setPixel(fg, x+i, y+j);
				}
			}
		}

		// checkerboard using bitmap drawing
		x = 90;
		y = 93;
		writeWindow(x, y, x+6, y+6);
		for(int i=0; i<24; i++) {
			writePair(fg, bg);
		}
		// last pixel wraps around to beginning so
		// we need to treat it as a special case
		writePair(fg, fg);

		// another checkerboard, with inverted colors
		x = 100;
		y = 93;
		writeWindow(x, y, x+6, y+6);
		for(int i=0; i<24; i++) {
			writePair(bg, fg);
		}
		// last pixel wraps around to beginning so
		// we need to treat it as a special case
		writePair(bg, bg);

		// show the hex codes for the colors we are using
		x = 2;
		y = 85;
		char label[14];
		strcpy(label, "fg:xxx bg:xxx");
		char *hexchr = "0123456789ABCDEF";
		label[3]  = hexchr[(fg>>8) & 0xf];
		label[4]  = hexchr[(fg>>4) & 0xf];
		label[5]  = hexchr[(fg   ) & 0xf];
		label[10] = hexchr[(bg>>8) & 0xf];
		label[11] = hexchr[(bg>>4) & 0xf];
		label[12] = hexchr[(bg   ) & 0xf];
		drawString(label, fg, bg, x, y);
		y += FONT_HEIGHT;

		// delay loop... prints an increasing hex number
		// and a binary bar code
		for(long j=0; j<1024; j++) {
			for(int i=0; i<4; i++) {
				label[i] = hexchr[(j>>(4*(3-i))) & 0xf];
			}
			label[4] = 0;
			drawString(label, fg, bg, x, y);
			for(int i=0; i<10; i++) {
				fillBox((j&(1<<i)) ? fg : bg,
					x+i*3, y+FONT_HEIGHT, 
					x+i*3+2, y+FONT_HEIGHT+4);
				fillBox((j&(1<<i)) ? bg : fg,
					x+i*3, y+FONT_HEIGHT+5, 
					x+i*3+2, y+FONT_HEIGHT+9);
			}
		}
	}
}